package it.uniroma2.util.math;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.util.Arrays;

public class MatrixUtils {
	
	public static double[][] loadDenseMatrix(File file, int rank) throws Exception
	{
		BufferedReader input = new BufferedReader(new FileReader(file));
		String[] dimensions = input.readLine().trim().split(" ");
		if (dimensions.length < 2)
			throw new Exception("Wrong format for dimensions row");
		int rows = rank > 0 ? rank : Integer.parseInt(dimensions[0]);
		int columns = Integer.parseInt(dimensions[1]);
		double[][] matrix = new double[rows][columns];
		for (int i=0; i<rows; i++)
		{
			String[] row = input.readLine().trim().split(" ");
			if (row.length != columns)
				throw new Exception("Wrong column number for row "+i+": "+columns+" expected, "+row.length+" found");
			for (int j=0; j<columns; j++)
				matrix[i][j] = Double.parseDouble(row[j]);
		}
		input.close();
		return matrix;		
	}
	
	public static double[][] loadSparseMatrix(File file) throws Exception
	{
		BufferedReader input = new BufferedReader(new FileReader(file));
		String[] dimensions = input.readLine().trim().split(" ");
		if (dimensions.length < 2)
			throw new Exception("Wrong format for dimensions row");
		int rows = Integer.parseInt(dimensions[0]);
		int columns = Integer.parseInt(dimensions[1]);
		double[][] matrix = new double[rows][columns];
		for (int i=0; i<rows; i++)
		{
			String[] row = input.readLine().trim().split("\t")[1].split(" ");
			for (int j=0; j<row.length; j++)
			{
				String[] featureValue = row[j].split(":");
				int featureIndex = Integer.parseInt(featureValue[0]);
				if (featureIndex > columns)
					throw new Exception("Wrong column number for row "+i+": "+columns+" expected, "+featureIndex+" found");
				// Feature number in svm format starts from 1
				matrix[i][featureIndex-1] = Double.parseDouble(featureValue[1]);
			}
		}
		input.close();
		return matrix;
	}
	
	public static double[] loadVector(File file, int rank) throws Exception
	{
		BufferedReader input = new BufferedReader(new FileReader(file));
		int dimension = Integer.parseInt(input.readLine());
		if (rank > 0)
			dimension = rank;
		double[] vector = new double[dimension];
		for (int i=0; i<dimension; i++)
			vector[i] = Double.parseDouble(input.readLine());
		input.close();
		return vector;
	}
	
	/**
	 * Performs matrix multiplication A*B. 
	 * Matrices A and B may be implicitly transposed before multiplication.
	 */
	public static double[][] multiply(double[][] A, double[][] B, boolean transposeA, boolean transposeB) throws Exception {
		if ((!transposeA && !transposeB && A[0].length != B.length) ||
				(transposeA && !transposeB && A.length != B.length) ||
				(!transposeA && transposeB && A[0].length != B[0].length) ||
				(transposeA && transposeB && A.length != B[0].length))
			throw new Exception("Matrixes can not be multiplied!");
		int rows = transposeA ? A[0].length : A.length;
		int columns = transposeB ? B.length : B[0].length;
		int addends = transposeA ? A.length : A[0].length;
//		System.out.print("Multiplying "+rows+"x"+columns);
		double out[][] = new double[rows][columns];
		double aElement, bElement;
		for (int i = 0 ; i < rows ; i++){
//			System.out.print(".");
			for (int j = 0 ; j < columns ; j++){
				for (int k = 0 ; k < addends ; k++){
					aElement = transposeA ? A[k][i] : A[i][k];
					bElement = transposeB ? B[j][k] : B[k][j];
					out[i][j] += aElement*bElement;
				}
			}
		}
//		System.out.println();
		return out;
	}
	
	public static Float[] multiply(Float[] rowUt_ith, Float[][] DT) throws Exception {
		
		Float[] row = new Float[DT[0].length];
		
		if (rowUt_ith.length == DT.length) {
			for (int i=0; i< DT[0].length ; i++) {
				row[i] = (float) 0.0;
				for (int j=0 ; j< rowUt_ith.length ; j++) row[i] += rowUt_ith[j] * DT[j][i];
			}
			
		} else {
			throw new Exception("Trying multiplying vector dim = " + rowUt_ith.length + " with matrix dim ="  + DT.length +"x" + DT[0].length);
		}
		
		
		return row;
	}

	public static Float[] multiply(Float[][] matrix, Float[] vector) throws Exception {
		
		Float[] row = new Float[matrix.length];
		
		if (vector.length == matrix[0].length) {
			for (int i=0; i< matrix[0].length ; i++) {
				row[i] = (float) 0.0;
				for (int j=0 ; j< vector.length ; j++) row[i] += matrix[i][j] * vector[j];
			}
			
		} else {
			throw new Exception("Trying multiplying vector dim = " + vector.length + " with matrix dim ="  + matrix.length +"x" + matrix[0].length);
		}
		return row;
	}
	
	public static double[] multiply(Float[][] matrix, double[] vector) throws Exception {
		
		double[] row = new double[matrix.length];
		
		if (vector.length == matrix[0].length) {
			for (int i=0; i< matrix[0].length ; i++) {
				row[i] = (float) 0.0;
				for (int j=0 ; j< vector.length ; j++) row[i] += ((double)matrix[i][j]) * vector[j];
			}
			
		} else {
			throw new Exception("Trying multiplying vector dim = " + vector.length + " with matrix dim ="  + matrix.length +"x" + matrix[0].length);
		}
		return row;
	}
	
	/**
	 * Performs matrix multiplication A*B. 
	 * Matrices A and B may be implicitly transposed before multiplication.
	 */
	public static Float[][] multiply(Float[][] A, Float[][] B, boolean transposeA, boolean transposeB) throws Exception {
		System.out.println("---> " + A.length + " " + A[0].length +  " : " + B.length + " " + B[0].length );
		
		if ((!transposeA && !transposeB && A[0].length != B.length) ||
				(transposeA && !transposeB && A.length != B.length) ||
				(!transposeA && transposeB && A[0].length != B[0].length) ||
				(transposeA && transposeB && A.length != B[0].length))
			throw new Exception("Matrixes can not be multiplied!");
		int rows = transposeA ? A[0].length : A.length;
		int columns = transposeB ? B.length : B[0].length;
		int addends = transposeA ? A.length : A[0].length;
		System.out.println("Multiplying "+rows+"x"+columns);
		Float out[][] = new Float[rows][columns];
		Float aElement, bElement;
		for (int i = 0 ; i < rows ; i++){
			if (i%100 == 0) System.out.print("."+i);
			for (int j = 0 ; j < columns ; j++){
				out[i][j] = new Float("0");
				for (int k = 0 ; k < addends ; k++){
					aElement = transposeA ? A[k][i] : A[i][k];
					bElement = transposeB ? B[j][k] : B[k][j];
					out[i][j] += aElement*bElement;
				}
			}
		}
		System.out.println(".done");
		return out;
	}

	
	
	
	public static void printToFileDense(double[][] matrix, File file) throws Exception
	{
		FileWriter output = new FileWriter(file);
		output.write(matrix.length + " " + matrix[0].length + "\n");
		for (int i=0; i<matrix.length; i++)
		{
			for (int j=0; j<matrix[i].length; j++)
			{
				output.write(String.valueOf(matrix[i][j]));
				if (j != matrix[i].length - 1)
					output.write(" ");
			}
			if (i != matrix.length - 1)
				output.write("\n");
		}
		output.close();
	}
	
	public static void printToFileSparse(double[][] matrix, File file) throws Exception
	{
		FileWriter output = new FileWriter(file);
		output.write(matrix.length + " " + matrix[0].length + "\n");
		for (int i=0; i<matrix.length; i++)
		{
			output.write("-1" + "\t");
			for (int j=0; j<matrix[i].length; j++)
			{
				if (matrix[i][j] != 0)
					output.write((j+1)+":"+String.valueOf(matrix[i][j]));
				if (j != matrix[i].length - 1)
					output.write(" ");
			}
			if (i != matrix.length - 1)
				output.write("\n");
		}
		output.close();
	}

	public static double[] uniformVector(int size, double value) {
		double[] result = new double[size];
		Arrays.fill(result, value);
		return result;
	}

}
